﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using NUnit.Framework;

namespace TaskFramework.Tests
{
    public enum RouteType
    {
        RouteSingleRequestAndDecrementRouteCount,
        RouteRequestInBatchAndSetRouteCountToZero,
        TransferTaskToPool
    }

    [TestFixture]
    public class AllInOneFixture
    {
        readonly Queue<TaskEvent> _taskEventQueue = new Queue<TaskEvent>();

        private void taskCompleted(TaskEvent info)
        {
            _taskEventQueue.Enqueue(info);
        }

        [TearDown]
        public void Reset()
        {
            _taskEventQueue.Clear();
        }

        

        [Test]
        public void should_route_a_request_via_a_task_dispatcher()
        {
            ITaskDispatcher dispatcher = new TaskDispatcher(new TaskPool() {WriteDebugInfo = true})
                                             {WriteDebugInfo = true};
            dispatcher.AddTaskFactory(new FetchTradesTaskFactory());
            dispatcher.TaskEvent += taskCompleted;

            dispatcher.RouteRequest(new FetchTradesRequest(1000));

            Thread.Sleep(200);

            Assert.AreEqual(2, _taskEventQueue.Count);
            TaskEvent info = _taskEventQueue.Dequeue();

            Assert.IsTrue(info.HasResult);
            Assert.IsNull(info.Exception);
            Assert.AreEqual(0, info.Info.RoutedCount);

            IList<Trade> trades = (IList<Trade>)info.Result;

            Assert.AreEqual(1, trades.Count);
            Assert.AreEqual(873603, trades[0].TradeId);
        }

        [Test]
        public void should_route_a_failing_request_via_a_task_dispatcher()
        {
            ITaskDispatcher dispatcher = new TaskDispatcher(new TaskPool() {WriteDebugInfo = true})
                                             {WriteDebugInfo = true};
            dispatcher.AddTaskFactory(new FetchTradesTaskFactory());
            dispatcher.TaskEvent += taskCompleted;

            dispatcher.RouteRequest(new FetchTradesRequest(true));

            Thread.Sleep(300);

            Assert.AreEqual(1, _taskEventQueue.Count);
            TaskEvent info = _taskEventQueue.Dequeue();

            Assert.IsFalse(info.HasResult);
            Assert.IsNotNull(info.Exception);
            Assert.IsNull(info.Result);
            Assert.AreEqual(0, info.Info.RoutedCount);

            Assert.IsInstanceOf<InvalidOperationException>(info.Exception);
        }

        [Test]
        public void should_reroute_request_direct_back_to_thread_pool()
        {
            ITaskDispatcher dispatcher = new TaskDispatcher(new TaskPool() {WriteDebugInfo = true})
                                             {WriteDebugInfo = true};
            dispatcher.AddTaskFactory(new FetchTradesTaskFactory());
            dispatcher.TaskEvent += taskCompleted;

            dispatcher.RouteRequest(new FetchTradesRequest(1000, 1, RouteType.TransferTaskToPool, 50));

            Thread.Sleep(100);

            Assert.AreEqual(2, _taskEventQueue.Count);
            TaskEvent info = _taskEventQueue.Dequeue();

            Assert.IsTrue(info.HasResult);
            Assert.IsNull(info.Exception);
            Assert.AreEqual(0, info.Info.RoutedCount);
            Assert.IsFalse(info.TaskCompleted);

            info = _taskEventQueue.Dequeue();

            Assert.IsFalse(info.HasResult);
            Assert.IsNull(info.Exception);
            Assert.IsTrue(info.TaskCompleted);
        }

        //[Test]
        //public void should_ensure_a_task_cache_cleans_up_references_when_count_is_zero()
        //{
        //    TaskCache cache = new TaskCache();

        //    TaskInfo info = new TaskInfo(new FetchTradesTask(new FetchTradesRequest(1000)), null);

        //    cache.Add(info);
        //    cache.Add(info);

        //    Assert.AreEqual(1, cache.Count);

        //    cache.Remove(info);

        //    Assert.AreEqual(1, cache.Count);

        //    cache.Remove(info);

        //    Assert.AreEqual(0, cache.Count);
        //}

        [Test]
        public void should_reroute_request_via_a_task_dispatcher()
        {
            ITaskDispatcher dispatcher = new TaskDispatcher(new TaskPool() {WriteDebugInfo = true})
                                             {WriteDebugInfo = true};
            dispatcher.AddTaskFactory(new FetchTradesTaskFactory());
            dispatcher.TaskEvent += taskCompleted;

            dispatcher.RouteRequest(new FetchTradesRequest(1000, 1, RouteType.RouteRequestInBatchAndSetRouteCountToZero, 20));

            Thread.Sleep(300);

            Assert.AreEqual(4, _taskEventQueue.Count);
            TaskEvent taskEvent = _taskEventQueue.Dequeue();

            Assert.IsTrue(taskEvent.HasResult);
            Assert.IsNull(taskEvent.Exception);
            Assert.IsFalse(taskEvent.TaskCompleted);
            Assert.AreEqual(1, taskEvent.Info.RoutedCount);

            taskEvent = _taskEventQueue.Dequeue();

            Assert.IsFalse(taskEvent.HasResult);
            Assert.IsNull(taskEvent.Exception);
            Assert.IsTrue(taskEvent.TaskCompleted);
            Assert.AreEqual(1, taskEvent.Info.RoutedCount);

            taskEvent = _taskEventQueue.Dequeue();

            Assert.IsTrue(taskEvent.HasResult);
            Assert.IsNull(taskEvent.Exception);
            Assert.IsFalse(taskEvent.TaskCompleted);
            Assert.AreEqual(0, taskEvent.Info.RoutedCount);

            taskEvent = _taskEventQueue.Dequeue();

            Assert.IsFalse(taskEvent.HasResult);
            Assert.IsNull(taskEvent.Exception);
            Assert.IsTrue(taskEvent.TaskCompleted);
            Assert.AreEqual(0, taskEvent.Info.RoutedCount);
        }

        [Test]
        public void should_ensure_that_task_pool_limits_concurrent_thread_count()
        {
            TaskPool taskPool = new TaskPool() {WriteDebugInfo = true};
            ITaskDispatcher dispatcher = new TaskDispatcher(taskPool) { WriteDebugInfo = true };
            dispatcher.AddTaskFactory(new FetchTradesTaskFactory());
            dispatcher.TaskEvent += taskCompleted;

            dispatcher.RouteRequest(new FetchTradesRequest(1000, 5, RouteType.RouteRequestInBatchAndSetRouteCountToZero, 400));

            Thread.Sleep(100);
            // Lapsed time: 100ms

            Assert.AreEqual(4, taskPool.ActiveTaskCount);
            Assert.AreEqual(0, _taskEventQueue.Count);

            Thread.Sleep(100);
            // Lapsed time: 200ms

            Assert.AreEqual(4, taskPool.ActiveTaskCount);
            Assert.AreEqual(0, _taskEventQueue.Count);

            Thread.Sleep(400);
            // Lapsed time: 600ms

            Assert.AreEqual(8, _taskEventQueue.Count);
            Assert.AreEqual(2, taskPool.ActiveTaskCount);

            Thread.Sleep(300);
            // Lapsed time: 900ms

            Assert.AreEqual(12, _taskEventQueue.Count);
        }

        [Test]
        public void ensure_correct_behavior_when_transferring_a_task_back_to_task_pool()
        {
            TaskPool taskPool = new TaskPool() { WriteDebugInfo = true };
            ITaskDispatcher dispatcher = new TaskDispatcher(taskPool) { WriteDebugInfo = true };
            dispatcher.AddTaskFactory(new FetchTradesTaskFactory());
            dispatcher.TaskEvent += taskCompleted;

            dispatcher.RouteRequest(new FetchTradesRequest(1000, 3, RouteType.TransferTaskToPool, 400));

            Thread.Sleep(200);
            // Lapsed time: 200ms

            Assert.AreEqual(1, taskPool.ActiveTaskCount);
            Assert.AreEqual(0, _taskEventQueue.Count);

            Thread.Sleep(350);
            // Lapsed time: 550ms

            Assert.AreEqual(2, _taskEventQueue.Count);
        }
    }
}
